/*
 * Copyright (c) 2024 Shenzhen Kaihong Digital Industry Development Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { DfuBaseService } from './DfuBaseService'
import { DfuServiceController } from './DfuServiceController'
import { WorkerMsg, WorkCmd } from './WorkMsg'
import worker, { MessageEvents, ErrorEvent } from '@ohos.worker';
import hilog from '@ohos.hilog';

export class DfuServiceInitiator {
  private static TAG: string = "DfuServiceInitiator";
  private static DOMAIN: number = 0x8632;

  public static DFU_CHANNEL_NAME: string = 'Progress';
  public static DFU_CHANNEL_DESCRIPTION: string = 'DFU operation progress.';
  public static DEFAULT_PRN_VALUE: number = 12;
  public static DEFAULT_MBR_SIZE: number = 0x1000;
  public static DEFAULT_SCAN_TIMEOUT: number = 5000; // ms

  /** Constant used to narrow the scope of the update to system components (SD+BL) only. */
  public static SCOPE_SYSTEM_COMPONENTS: number = 1;
  /** Constant used to narrow the scope of the update to application only. */
  public static SCOPE_APPLICATION: number = 2;

  private deviceAddress: string = '';
  private deviceName: string = '';

  private disableNotification: boolean = false;
  private startAsForegroundService: boolean = true;

  private fileUri: string;
  private filePath: string;
  private fileResId: number;

  private initFileUri: string;
  private initFilePath: string;
  private initFileResId: number;

  private mimeType: string;
  private fileType: number = -1;

  private keepBond: boolean;
  private restoreBond: boolean;
  private forceDfu: boolean = false;
  private forceScanningForNewAddressInLegacyDfu: boolean = false;
  private enableUnsafeExperimentalButtonlessDfu: boolean = false;
  private isDisableResume: boolean = false;
  private numberOfRetries: number = 0; // 0 to be backwards compatible
  private mbrSize: number = DfuServiceInitiator.DEFAULT_MBR_SIZE;
  private dataObjectDelay: number = 0; // initially disabled
  private rebootTime: number = 0; // ms
  private scanTimeout: number = DfuServiceInitiator.DEFAULT_SCAN_TIMEOUT; // ms

  private packetReceiptNotificationsEnabled: boolean;
  private numberOfPackets: number = 12;

  private mtu: number = 517;
  private currentMtu: number = 23;

  private legacyDfuUuids: string[];
  private secureDfuUuids: string[];
  private experimentalButtonlessDfuUuids: string[];
  private buttonlessDfuWithoutBondSharingUuids: string[];
  private buttonlessDfuWithBondSharingUuids: string[];

  constructor(deviceAddress: string) {
    this.deviceAddress = deviceAddress;
  }
  /**
   * Creates the builder. Use setZip(...), or setBinOrHex(...) methods to specify the file you
   * want to upload. In the latter case an init file may also be set using the setInitFile(...)
   * method. Init files are required by DFU Bootloader version 0.5 or newer (SDK 7.0.0+).
   *
   * @param deviceAddress the target device device address
   */
  public DfuServiceInitiator(deviceAddress: string) {
    this.deviceAddress = deviceAddress;
  }

  /**
   * Sets the device name. The device name is not required. It's written in the notification
   * during the DFU process. If not set the
   * {@link no.nordicsemi.android.dfu.R.string#dfu_unknown_name R.string.dfu_unknown_name}
   * value will be used.
   *
   * @param name the device name (optional)
   * @return the builder
   */
  public setDeviceName(name: string): DfuServiceInitiator {
    this.deviceName = name;
    return this;
  }

  /**
   * Sets whether the progress notification in the status bar should be disabled.
   * Defaults to false.
   *
   * @param disableNotification whether to disable the notification
   * @return the builder
   */
  public setDisableNotification(disableNotification: boolean): DfuServiceInitiator {
    this.disableNotification = disableNotification;
    return this;
  }

  /**
   * Sets whether the DFU service should be started as a foreground service. By default it's
   * <i>true</i>. According to
   * <a href="https://developer.android.com/about/versions/oreo/background.html">
   * https://developer.android.com/about/versions/oreo/background.html</a>
   * the background service may be killed by the system on Android Oreo after user quits the
   * application so it is recommended to keep it as a foreground service (default) at least on
   * Android Oreo+.
   *
   * @param foreground whether the service should be started in foreground state.
   * @return the builder
   */
  public setForeground(foreground: boolean): DfuServiceInitiator {
    this.startAsForegroundService = foreground;
    return this;
  }

  /**
   * Sets whether the bond information should be preserver after flashing new application.
   * This feature requires Legacy DFU Bootloader version 0.6 or newer (SDK 8.0.0+).
   * Please see the {@link DfuBaseService#EXTRA_KEEP_BOND} for more information regarding
   * requirements.
   * <p>
   * This flag is ignored when Secure DFU Buttonless Service is used. It will keep or remove the
   * bond depending on the Buttonless service type.
   * <p>
   * <b>Important:</b> The flag does not ensure that the DFU is performed on an encrypted link.
   * If the bond information is present only on Android side, but not on the peripheral side,
   * Android (version 4.3-10) will connect without encryption. On those versions it is not possible
   * to ensure the link is truly encrypted, as {@link BluetoothDevice#getBondState()} returns
   * {@link BluetoothDevice#BOND_BONDED} also if bonding isn't used.
   *
   * @param keepBond whether the bond information should be preserved in the new application.
   * @return the builder
   */
  public setKeepBond(keepBond: boolean): DfuServiceInitiator {
    this.keepBond = keepBond;
    return this;
  }

  /**
   * Sets whether a new bond should be created after the DFU is complete. The old bond
   * information will be removed before.
   * Please see the {@link DfuBaseService#EXTRA_RESTORE_BOND} for more information regarding
   * requirements.
   * <p>
   * This flag is ignored when Secure DFU Buttonless Service is used. It will keep or will not
   * restore the bond depending on the Buttonless service type.
   *
   * @param restoreBond whether the bond should be created after the DFU is complete.
   * @return the builder
   */
  public setRestoreBond(restoreBond: boolean): DfuServiceInitiator {
    this.restoreBond = restoreBond;
    return this;
  }

  /**
   * This method sets the duration of a delay, that the service will wait before sending each
   * data object in Secure DFU. The delay will be done after a data object is created, and before
   * any data byte is sent. The default value is 0, which disables this feature.
   * <p>
   * It has been found, that a delay of at least 300ms reduces the risk of packet lose (the
   * bootloader needs some time to prepare flash memory) on DFU bootloader from SDK 15 and 16.
   * The delay does not have to be longer than 400 ms, as according to performed tests, such delay
   * is sufficient.
   * <p>
   * The longer the delay, the more time DFU will take to complete (delay will be repeated for
   * each data object (4096 bytes)). However, with too small delay a packet lose may occur,
   * causing the service to enable PRN and set them to 1 making DFU process very, very slow
   * (but reliable).
   *
   * @param delay the initial delay that the service will wait before sending each data object.
   * @since 1.10
   * @return the builder
   */
  public setPrepareDataObjectDelay(delay: number): DfuServiceInitiator {
    this.dataObjectDelay = delay;
    return this;
  }

  /**
   * Enables or disables the Packet Receipt Notification (PRN) procedure.
   * <p>
   * By default the PRNs are disabled on devices with Android Marshmallow or newer and enabled on
   * older ones.
   *
   * @param enabled true to enabled PRNs, false to disable
   * @return the builder
   * @see DfuSettingsConstants#SETTINGS_PACKET_RECEIPT_NOTIFICATION_ENABLED
   */
  public setPacketsReceiptNotificationsEnabled(enabled: boolean): DfuServiceInitiator {
    this.packetReceiptNotificationsEnabled = enabled;
    return this;
  }

  /**
   * If Packet Receipt Notification procedure is enabled, this method sets number of packets to
   * be sent before receiving a PRN. A PRN is used to synchronize the transmitter and receiver.
   * <p>
   * If the value given is equal to 0, the {@link #DEFAULT_PRN_VALUE} will be used instead.
   * <p>
   * To disable PRNs use {@link #setPacketsReceiptNotificationsEnabled(boolean)}.
   *
   * @param number number of packets to be sent before receiving a PRN. Defaulted when set to 0.
   * @return the builder
   * @see #setPacketsReceiptNotificationsEnabled(boolean)
   * @see DfuSettingsConstants#SETTINGS_NUMBER_OF_PACKETS
   */
  public setPacketsReceiptNotificationsValue(nop: number): DfuServiceInitiator {
    this.numberOfPackets = nop > 0 ? nop : DfuServiceInitiator.DEFAULT_PRN_VALUE;
    return this;
  }

  /**
   * Setting force DFU to true will prevent from jumping to the DFU Bootloader
   * mode in case there is no DFU Version characteristic (Legacy DFU only!).
   * Use it if the DFU operation can be handled by your device running in the application mode.
   * <p>
   * If the DFU Version characteristic exists, the
   * information whether to begin DFU operation, or jump to bootloader, is taken from that
   * characteristic's value. The value returned equal to 0x0100 (read as: minor=1, major=0, or
   * version 0.1) means that the device is in the application mode and buttonless jump to
   * DFU Bootloader is supported.
   * <p>
   * However, if there is no DFU Version characteristic, a device
   * may support only Application update (version from SDK 4.3.0), may support Soft Device,
   * Bootloader and Application update but without buttonless jump to bootloader (SDK 6.0.0)
   * or with buttonless jump (SDK 6.1.0).
   * <p>
   * In the last case, the DFU Library determines whether the device is in application mode or in
   * DFU Bootloader mode by counting number of services: if no DFU Service found - device is in
   * app mode and does not support buttonless jump, if the DFU Service is the only service found
   * (except General Access and General Attribute services) - it assumes it is in DFU Bootloader
   * mode and may start DFU immediately, if there is at least one service except DFU Service -
   * the device is in application mode and supports buttonless jump. In the last case, if you
   * want to perform DFU operation without jumping - call the this method with parameter equal
   * to true.
   * <p>
   * This method is ignored in Secure DFU.
   *
   * @param force true to ensure the DFU will start if there is no DFU Version characteristic
   *              (Legacy DFU only)
   * @return the builder
   * @see DfuSettingsConstants#SETTINGS_ASSUME_DFU_NODE
   */
  public setForceDfu(force: boolean): DfuServiceInitiator {
    this.forceDfu = force;
    return this;
  }

  /**
   * Sets the time required by the device to reboot. The library will wait for this time before
   * scanning for the device in bootloader mode.
   *
   * @param rebootTime the reboot time in milliseconds, default 0.
   * @return the builder
   * @since 2.1
   */
  public setRebootTime(rebootTime: number): DfuServiceInitiator {
    this.rebootTime = rebootTime;
    return this;
  }

  /**
   * Sets the scan duration (in milliseconds) when scanning for DFU Bootloader.
   *
   * @param scanTimeout the scan duration in milliseconds, default {@value #DEFAULT_SCAN_TIMEOUT} ms.
   * @return the builder
   * @since 2.1
   */
  public setScanTimeout(scanTimeout: number): DfuServiceInitiator {
    this.scanTimeout = scanTimeout;
    return this;
  }

  /**
   * When this is set to true, the Legacy Buttonless Service will scan for the device advertising
   * with an incremented MAC address, instead of trying to reconnect to the same device.
   * <p>
   * Setting this to true requires modifying the buttonless service on the device not to share the
   * peer data with the bootloader, or modifying the bootloader to always advertise with MAC+1.
   * Setting it to true with a default implementation of the buttonless service should work, but
   * is pointless.
   * <p>
   * This is a feature equivalent to
   * <a href="https://github.com/NordicSemiconductor/IOS-Pods-DFU-Library/pull/374">PR #374</a>
   * in DFU library for iOS.
   * @param force set to true when your bootloader is advertising with an incremented MAC address.
   *              By default, in Legacy DFU, the bootloader uses the same MAC address and is
   *              advertising directly. This does not seen to work on some phones (Samsung) with
   *              recent Android versions.
   * @return the builder
   */
  public setForceScanningForNewAddressInLegacyDfu(force: boolean): DfuServiceInitiator {
    this.forceScanningForNewAddressInLegacyDfu = force;
    return this;
  }

  /**
   * This options allows to disable the resume feature in Secure DFU. When the extra value is set
   * to true, the DFU will send Init Packet and Data again, despite the firmware might have been
   * send partially before. By default, without setting this extra, or by setting it to false,
   * the DFU will resume the previously cancelled upload if CRC values match.
   * <p>
   * It is ignored when Legacy DFU is used.
   * <p>
   * This feature seems to help in some cases:
   * <a href="https://github.com/NordicSemiconductor/Android-DFU-Library/issues/71">#71</a>.
   *
   * @return the builder
   */
  public disableResume(): DfuServiceInitiator {
    this.isDisableResume = true;
    return this;
  }

  /**
   * Sets the number of retries that the DFU service will use to complete DFU. The default
   * value is 0, for backwards compatibility reason.
   * <p>
   * If the given value is greater than 0, the service will restart itself at most {@code max}
   * times in case of an undesired disconnection during DFU operation. This attempt counter
   * is independent from another counter, for reconnection attempts, which is equal to 3.
   * The latter one will be used when connection will fail with an error (possible packet
   * collision or any other reason). After successful connection, the reconnection counter is
   * reset, while the retry counter is cleared after a DFU finishes with success.
   * <p>
   * The service will not try to retry DFU in case of any other error, for instance an error
   * sent from the target device.
   *
   * @param max Maximum number of retires to complete DFU. Usually around 2.
   * @return the builder
   */
  public setNumberOfRetries(max: number): DfuServiceInitiator {
    this.numberOfRetries = max;
    return this;
  }

  /**
   * Sets the Maximum Transfer Unit (MTU) value that the Secure DFU service will try to request
   * before performing DFU. By default, value 517 will be used, which is the highest supported
   * by Android. However, as the highest supported MTU by the Secure DFU from SDK 15
   * (first which supports higher MTU) is 247, the sides will agree on using MTU = 247 instead
   * if the phone supports it (Lollipop or newer device).
   * <p>
   * The higher the MTU, the faster the data may be sent.
   * <p>
   * If you encounter problems with high MTU, you may lower the required value using this method.
   * See: <a href="https://github.com/NordicSemiconductor/Android-DFU-Library/issues/111">Issue 111</a>
   * <p>
   * To disable requesting MTU, use value 0, or {@link #disableMtuRequest()}.
   * <p>
   * Note: Higher (that is greater then 23, 517) MTUs are supported on Lollipop or newer Android
   * devices, and on DFU bootloader from SDK 15 or newer (Secure DFU only).
   *
   * @param mtu the MTU that wil be requested, 0 to disable MTU request.
   * @return the builder
   */
  public setMtu(mtu: number): DfuServiceInitiator {
    this.mtu = mtu;
    return this;
  }

  /**
   * Sets the current MTU value. This method should be used only if the device is already
   * connected and MTU has been requested before DFU service is started.
   * The SoftDevice allows to change MTU only once, while the following requests fail with
   * Invalid PDU error. In case this error is received, the MTU will be set to the value
   * specified using this method. There is no verification of this value. If it's set to
   * too high value, some of the packets will not be sent and DFU will not succeed.
   * <p>
   * By default value 23 - 517 is used for compatibility reasons.
   * <p>
   * Higher MTU values were supported since SDK 15.0.
   *
   * @param mtu the MTU value received in
   *            {@link android.bluetooth.BluetoothGattCallback#onMtuChanged(BluetoothGatt, int, int)} or
   *            {@link android.bluetooth.BluetoothGattServerCallback#onMtuChanged(BluetoothDevice, int)}.
   * @return the builder
   */
  public setCurrentMtu(mtu: number): DfuServiceInitiator {
    this.currentMtu = mtu;
    return this;
  }

  /**
   * Disables MTU request.
   *
   * @return the builder
   * @see #setMtu(int)
   */
  public disableMtuRequest(): DfuServiceInitiator {
    this.mtu = 0;
    return this;
  }

  /**
   * This method allows to narrow the update to selected parts from the ZIP, for example
   * to allow only application update from a ZIP file that has SD+BL+App. System components scope
   * include the Softdevice and/or the Bootloader (they can't be separated as they are packed in
   * a single bin file and the library does not know whether it contains only the softdevice,
   * the bootloader or both) Application scope includes the application only.
   *
   * @param scope the update scope, one of {@link #SCOPE_SYSTEM_COMPONENTS} or
   *              {@link #SCOPE_APPLICATION}.
   * @return the builder
   */
  public setScope(scope: number): DfuServiceInitiator {
    if (!(DfuBaseService.MIME_TYPE_ZIP === this.mimeType))
      hilog.info(DfuServiceInitiator.DOMAIN, DfuServiceInitiator.TAG, "Scope can be set only for a ZIP file");
    if (scope == DfuServiceInitiator.SCOPE_APPLICATION)
      this.fileType = DfuBaseService.TYPE_APPLICATION;
    else if (scope == DfuServiceInitiator.SCOPE_SYSTEM_COMPONENTS)
      this.fileType = DfuBaseService.TYPE_SOFT_DEVICE | DfuBaseService.TYPE_BOOTLOADER;
    else if (scope == (DfuServiceInitiator.SCOPE_APPLICATION | DfuServiceInitiator.SCOPE_SYSTEM_COMPONENTS))
      this.fileType = DfuBaseService.TYPE_AUTO;
    else {
      hilog.info(DfuServiceInitiator.DOMAIN, DfuServiceInitiator.TAG, "Unknown scope");
    }
    return this;
  }

  /**
   * This method sets the size of an MBR (Master Boot Record). It should be used only
   * when updating a file from a HEX file. If you use BIN or ZIP, value set here will
   * be ignored.
   * <p>
   * The MBR size is important for the HEX parser, which has to cut it from the Soft Device's
   * HEX before sending it to the DFU target. The MBR can't be updated using DFU, and the
   * bootloader expects only the Soft Device bytes. Usually, the Soft Device HEX provided
   * by Nordic contains an MBR at addresses 0x0000 to 0x1000.
   * 0x1000 is the default size of MBR which will be used.
   * <p>
   * If you have a HEX file which address start from 0 and want to send the whole BIN content
   * from it, you have to set the MBR size to 0, otherwise first 4096 bytes will be cut off.
   * <p>
   * The value set here will not be used if the {@link DfuSettingsConstants#SETTINGS_MBR_SIZE}
   * is set in Shared Preferences.
   *
   * @param mbrSize the MBR size in bytes. Defaults to 4096 (0x1000) bytes.
   * @return the builder
   * @see DfuSettingsConstants#SETTINGS_MBR_SIZE
   */
  public setMbrSize(mbrSize: number) {
    this.mbrSize = mbrSize;
    return this;
  }

  /**
   * Set this flag to true to enable experimental buttonless feature in Secure DFU. When the
   * experimental Buttonless DFU Service is found on a device, the service will use it to
   * switch the device to the bootloader mode, connect to it in that mode and proceed with DFU.
   * <p>
   * <b>Please, read the information below before setting it to true.</b>
   * <p>
   * In the SDK 12.x the Buttonless DFU feature for Secure DFU was experimental.
   * It is NOT recommended to use it: it was not properly tested, had implementation bugs (e.g.
   * <a href="https://devzone.nordicsemi.com/question/100609/sdk-12-bootloader-erased-after-programming/">link</a>)
   * and does not required encryption and therefore may lead to DOS attack (anyone can use it to
   * switch the device to bootloader mode). However, as there is no other way to trigger
   * bootloader mode on devices without a button, this DFU Library supports this service, but the
   * feature must be explicitly enabled here. Be aware, that setting this flag to false will no
   * protect your devices from this kind of attacks, as an attacker may use another app for that
   * purpose. To be sure your device is secure remove this experimental service from your device.
   * <p>
   * <b>Spec:</b><br>
   * Buttonless DFU Service UUID: 8E400001-F315-4F60-9FB8-838830DAEA50<br>
   * Buttonless DFU characteristic UUID: 8E400001-F315-4F60-9FB8-838830DAEA50 (the same)<br>
   * Enter Bootloader Op Code: 0x01<br>
   * Correct return value: 0x20-01-01 , where:<br>
   * 0x20 - Response Op Code<br>
   * 0x01 - Request Code<br>
   * 0x01 - Success<br>
   * The device should disconnect and restart in DFU mode after sending the notification.
   * <p>
   * The Buttonless service has changed in SDK 13 and later. Indications are used instead of
   * notifications. Also, Buttonless service for bonded devices has been added.
   * It is recommended to use any of the new services instead.
   *
   * @return the builder
   */
  public setUnsafeExperimentalButtonlessServiceInSecureDfuEnabled(enable: boolean): DfuServiceInitiator {
    this.enableUnsafeExperimentalButtonlessDfu = enable;
    return this;
  }

  /**
   * Sets custom UUIDs for Legacy DFU and Legacy Buttonless DFU. Use this method if your DFU
   * implementation uses different UUID for at least one of the given UUIDs.
   * Parameter set to <code>null</code> will reset the UUID to the default value.
   *
   * @param dfuServiceUuid      custom Legacy DFU service UUID or null, if default is to be used
   * @param dfuControlPointUuid custom Legacy DFU Control Point characteristic UUID or null,
   *                            if default is to be used
   * @param dfuPacketUuid       custom Legacy DFU Packet characteristic UUID or null, if default is
   *                            to be used
   * @param dfuVersionUuid      custom Legacy DFU Version characteristic UUID or null,
   *                            if default is to be used (SDK 7.0 - 11.0 only, set null for earlier SDKs)
   * @return the builder
   */
  public setCustomUuidsForLegacyDfu(dfuServiceUuid: string,
    dfuControlPointUuid: string, dfuPacketUuid: string,
    dfuVersionUuid: string): DfuServiceInitiator {
    let uuids: Array<string> = new Array(4);
    uuids[0] = dfuServiceUuid;
    uuids[1] = dfuControlPointUuid;
    uuids[2] = dfuPacketUuid;
    uuids[3] = dfuVersionUuid;
    this.legacyDfuUuids = uuids;
    return this;
  }

  /**
   * Sets custom UUIDs for Secure DFU. Use this method if your DFU implementation uses different
   * UUID for at least one of the given UUIDs. Parameter set to <code>null</code> will reset
   * the UUID to the default value.
   *
   * @param dfuServiceUuid      custom Secure DFU service UUID or null, if default is to be used
   * @param dfuControlPointUuid custom Secure DFU Control Point characteristic UUID or null,
   *                            if default is to be used
   * @param dfuPacketUuid       custom Secure DFU Packet characteristic UUID or null, if default
   *                            is to be used
   * @return the builder
   */
  public setCustomUuidsForSecureDfu(dfuServiceUuid: string,
    dfuControlPointUuid: string,
    dfuPacketUuid: string): DfuServiceInitiator {
    let uuids: Array<string> = new Array(3);
    uuids[0] = dfuServiceUuid;
    uuids[1] = dfuControlPointUuid;
    uuids[2] = dfuPacketUuid;
    this.secureDfuUuids = uuids;
    return this;
  }

  /**
   * Sets custom UUIDs for the experimental Buttonless DFU Service from SDK 12.x. Use this method
   * if your DFU implementation uses different UUID for at least one of the given UUIDs.
   * Parameter set to <code>null</code> will reset the UUID to the default value.
   * <p>
   * Remember to call {@link #setUnsafeExperimentalButtonlessServiceInSecureDfuEnabled(boolean)}
   * with parameter <code>true</code> if you intent to use this service.
   *
   * @param buttonlessDfuServiceUuid      custom Buttonless DFU service UUID or null, if default
   *                                      is to be used
   * @param buttonlessDfuControlPointUuid custom Buttonless DFU characteristic UUID or null,
   *                                      if default is to be used
   * @return the builder
   */
  public setCustomUuidsForExperimentalButtonlessDfu(buttonlessDfuServiceUuid: string,
    buttonlessDfuControlPointUuid: string): DfuServiceInitiator {
    let uuids: Array<string> = new Array(2);
    uuids[0] = buttonlessDfuServiceUuid;
    uuids[1] = buttonlessDfuControlPointUuid;
    this.experimentalButtonlessDfuUuids = uuids;
    return this;
  }

  /**
   * Sets custom UUIDs for the Buttonless DFU Service from SDK 14 (or later).
   * Use this method if your DFU implementation uses different UUID for at least one of the given
   * UUIDs. Parameter set to <code>null</code> will reset the UUID to the default value.
   *
   * @param buttonlessDfuServiceUuid      custom Buttonless DFU service UUID or null, if default
   *                                      is to be used
   * @param buttonlessDfuControlPointUuid custom Buttonless DFU characteristic UUID or null,
   *                                      if default is to be used
   * @return the builder
   */
  public setCustomUuidsForButtonlessDfuWithBondSharing(buttonlessDfuServiceUuid: string,
    buttonlessDfuControlPointUuid: string): DfuServiceInitiator{
    let uuids: Array<string> = new Array(2);
    uuids[0] = buttonlessDfuServiceUuid;
    uuids[1] = buttonlessDfuControlPointUuid;
    this.buttonlessDfuWithBondSharingUuids = uuids;
    return this;
  }

  /**
   * Sets custom UUIDs for the Buttonless DFU Service from SDK 13. Use this method if your DFU
   * implementation uses different UUID for at least one of the given UUIDs.
   * Parameter set to <code>null</code> will reset the UUID to the default value.
   *
   * @param buttonlessDfuServiceUuid      custom Buttonless DFU service UUID or null, if default
   *                                      is to be used
   * @param buttonlessDfuControlPointUuid custom Buttonless DFU characteristic UUID or null,
   *                                      if default is to be used
   * @return the builder
   */
  public setCustomUuidsForButtonlessDfuWithoutBondSharing(buttonlessDfuServiceUuid: string,
    buttonlessDfuControlPointUuid: string): DfuServiceInitiator {
    let uuids: Array<string> = new Array(2);
    uuids[0] = buttonlessDfuServiceUuid;
    uuids[1] = buttonlessDfuControlPointUuid;
    this.buttonlessDfuWithoutBondSharingUuids = uuids;
    return this;
  }

  /**
   * Sets the URI to the Distribution packet (ZIP) or to a ZIP file matching the deprecated naming
   * convention.
   *
   * @param uri the URI of the file
   * @return the builder
   * @see #setZip(String)
   * @see #setZip(int)
   */
  public setZipUri(uri: string): DfuServiceInitiator {
    return this.initWithType(uri, null, 0, DfuBaseService.TYPE_AUTO, DfuBaseService.MIME_TYPE_ZIP);
  }

  /**
   * Sets the path to the Distribution packet (ZIP) or the a ZIP file matching the deprecated naming
   * convention.
   *
   * @param path path to the file
   * @return the builder
   * @see #setZip(Uri)
   * @see #setZip(int)
   */
  public setZipPath(path: string): DfuServiceInitiator {
    return this.initWithType(null, path, 0, DfuBaseService.TYPE_AUTO, DfuBaseService.MIME_TYPE_ZIP);
  }

  /**
   * Sets the resource ID of the Distribution packet (ZIP) or the a ZIP file matching the
   * deprecated naming convention. The file should be in the /res/raw folder.
   *
   * @param rawResId file's resource ID
   * @return the builder
   * @see #setZip(Uri)
   * @see #setZip(String)
   */
  public setZipResId(rawResId: number): DfuServiceInitiator {
    return this.initWithType(null, null, rawResId, DfuBaseService.TYPE_AUTO, DfuBaseService.MIME_TYPE_ZIP);
  }

  /**
   * Sets the URI or path of the ZIP file.
   * At least one of the parameters must not be null.
   * If the URI and path are not null the URI will be used.
   *
   * @param uri  the URI of the file
   * @param path the path of the file
   * @return the builder
   */
  public setZipUriPath(uri: string, path: string): DfuServiceInitiator {
    return this.initWithType(uri, path, 0, DfuBaseService.TYPE_AUTO, DfuBaseService.MIME_TYPE_ZIP);
  }

  /**
   * Sets the URI of the BIN or HEX file containing the new firmware.
   * For DFU Bootloader version 0.5 or newer the init file must be specified using one of
   * {@link #setInitFile(Uri)} methods.
   *
   * @param fileType the file type, a bit field created from:
   *                 <ul>
   *                 <li>{@link DfuBaseService#TYPE_APPLICATION} - the Application will be sent</li>
   *                 <li>{@link DfuBaseService#TYPE_SOFT_DEVICE} - he Soft Device will be sent</li>
   *                 <li>{@link DfuBaseService#TYPE_BOOTLOADER} - the Bootloader will be sent</li>
   *                 </ul>
   * @param uri      the URI of the file
   * @return the builder
   */
  public setBinOrHexUri(fileType: number, uri: string): DfuServiceInitiator {
    if (fileType == DfuBaseService.TYPE_AUTO)
      throw ("You must specify the file type");
    return this.initWithType(uri, null, 0, fileType, DfuBaseService.MIME_TYPE_OCTET_STREAM);
  }

  /**
   * Sets the URI of the BIN or HEX file containing the new firmware.
   * For DFU Bootloader version 0.5 or newer the init file must be specified using one of
   * {@link #setInitFile(String)} methods.
   *
   * @param fileType see {@link #setBinOrHex(int, Uri)} for details
   * @param path     path to the file
   * @return the builder
   */
  public setBinOrHexPath(fileType: number, path: string): DfuServiceInitiator {
    if (fileType == DfuBaseService.TYPE_AUTO)
      throw ("You must specify the file type");
    return this.initWithType(null, path, 0, fileType, DfuBaseService.MIME_TYPE_OCTET_STREAM);
  }

  /**
   * Sets the URI or path to the BIN or HEX file containing the new firmware.
   * For DFU Bootloader version 0.5 or newer the init file must be specified using one of
   * {@link #setInitFile(String)} methods.
   *
   * @param fileType see {@link #setBinOrHex(int, Uri)} for details
   * @param uri      the URI of the file
   * @param path     path to the file
   * @return the builder
   * @deprecated The Distribution packet (ZIP) should be used for DFU Bootloader version 0.5 or newer
   */
  public setBinOrHexUriPath(fileType: number, uri: string, path: string): DfuServiceInitiator {
    if (fileType == DfuBaseService.TYPE_AUTO)
      throw ("You must specify the file type");
    return this.initWithType(uri, path, 0, fileType, DfuBaseService.MIME_TYPE_OCTET_STREAM);
  }

  /**
   * Sets the resource ID pointing the BIN or HEX file containing the new firmware.
   * The file should be in the /res/raw folder. For DFU Bootloader version 0.5 or newer the init
   * file must be specified using one of {@link #setInitFile(int)} methods.
   *
   * @param fileType see {@link #setBinOrHex(int, Uri)} for details
   * @param rawResId resource ID
   * @return the builder
   */
  public setBinOrHexResId(fileType: number, rawResId: number): DfuServiceInitiator {
    if (fileType == DfuBaseService.TYPE_AUTO)
      throw "You must specify the file type";
    return this.initWithType(null, null, rawResId, fileType, DfuBaseService.MIME_TYPE_OCTET_STREAM);
  }

  /**
   * Sets the URI of the Init file. The init file for DFU Bootloader version pre-0.5
   * (SDK 4.3, 6.0, 6.1) contains only the CRC-16 of the firmware.
   * Bootloader version 0.5 or newer requires the Extended Init Packet.
   *
   * @param initFileUri the URI of the init file
   * @return the builder
   */
  public setInitFileUri(initFileUri: string): DfuServiceInitiator {
    return this.init(initFileUri, null, 0);
  }

  /**
   * Sets the path to the Init file. The init file for DFU Bootloader version pre-0.5
   * (SDK 4.3, 6.0, 6.1) contains only the CRC-16 of the firmware.
   * Bootloader version 0.5 or newer requires the Extended Init Packet.
   *
   * @param initFilePath the path to the init file
   * @return the builder
   */
  public setInitFilePath(initFilePath: string): DfuServiceInitiator {
    return this.init(null, initFilePath, 0);
  }

  /**
   * Sets the resource ID of the Init file. The init file for DFU Bootloader version pre-0.5
   * (SDK 4.3, 6.0, 6.1) contains only the CRC-16 of the firmware.
   * Bootloader version 0.5 or newer requires the Extended Init Packet.
   *
   * @param initFileResId the resource ID of the init file
   * @return the builder
   */
  public setInitFileResId(initFileResId: number): DfuServiceInitiator {
    return this.init(null, null, initFileResId);
  }

  /**
   * Sets the URI or path to the Init file. The init file for DFU Bootloader version pre-0.5
   * (SDK 4.3, 6.0, 6.1) contains only the CRC-16 of the firmware. Bootloader version 0.5 or newer
   * requires the Extended Init Packet. If the URI and path are not null the URI will be used.
   *
   * @param initFileUri  the URI of the init file
   * @param initFilePath the path of the init file
   * @return the builder
   */
  public setInitFileUriPath(initFileUri: string, initFilePath: string): DfuServiceInitiator {
    return this.init(initFileUri, initFilePath, 0);
  }

  /**
   * Starts the DFU service.
   *
   * @param context the application context
   * @param service the class derived from the BaseDfuService
   */
  public start(): DfuServiceController {
    if (this.fileType == -1) {
      hilog.info(DfuServiceInitiator.DOMAIN, DfuServiceInitiator.TAG,
        "You must specify the firmware file before starting the service");
      throw ("You must specify the firmware file before starting the service");
    }


    const workerInstance = new worker.ThreadWorker("./workers/DfuWorker.ets");

    // TODO: 这部分要变成数据结构传给worker
    let wmsg: WorkerMsg = {
      cmd: WorkCmd.WC_START_SERVICE,
      deviceAddress: this.deviceAddress,
      deviceName: this.deviceName,
      disableNotification: this.disableNotification,
      startAsForegroundService: this.startAsForegroundService,
      mimeType: this.mimeType,
      fileType: this.fileType,
      fileUri: this.fileUri,
      filePath: this.filePath,
      fileResId: this.fileResId,
      initFileUri: this.initFileUri,
      initFilePath: this.initFilePath,
      initFileResId: this.initFileResId,
      keepBond: this.keepBond,
      restoreBond: this.restoreBond,
      forceDfu: this.forceDfu,
      forceScanningForNewAddressInLegacyDfu: this.forceScanningForNewAddressInLegacyDfu,
      disableResume: this.isDisableResume,
      numberOfRetries: this.numberOfRetries,
      mbrSize: this.mbrSize,
      dataObjectDelay: this.dataObjectDelay,
      scanTimeout: this.scanTimeout,
      rebootTime: this.rebootTime,
      mtu: this.mtu,
      currentMtu: this.currentMtu,
      enableUnsafeExperimentalButtonlessDfu: this.enableUnsafeExperimentalButtonlessDfu,
      packetReceiptNotificationsEnabled: this.packetReceiptNotificationsEnabled,
      numberOfPackets: this.numberOfPackets,
      legacyDfuUuids: this.legacyDfuUuids,
      secureDfuUuids: this.secureDfuUuids,
      experimentalButtonlessDfuUuids: this.experimentalButtonlessDfuUuids,
      buttonlessDfuWithoutBondSharingUuids: this.buttonlessDfuWithoutBondSharingUuids,
      buttonlessDfuWithBondSharingUuids: this.buttonlessDfuWithBondSharingUuids,
    }
    let data: string = JSON.stringify(wmsg);
    workerInstance.postMessage(data);

    workerInstance.onmessage= (e: MessageEvents): void => {
      // data：worker线程发送的信息
      let indata: string = e.data;
      hilog.info(DfuServiceInitiator.DOMAIN, DfuServiceInitiator.TAG, `mainthread onmessage: ${indata}`);
      switch (indata) {
        case WorkCmd.WC_DISCONNECT.toString():
          hilog.info(DfuServiceInitiator.DOMAIN, DfuServiceInitiator.TAG, `WorkCmd.WC_DISCONNECT msg in`);
          break;
        default:
          hilog.info(DfuServiceInitiator.DOMAIN, DfuServiceInitiator.TAG, `msg in & post ${indata}`);
          let newMsg: WorkerMsg = JSON.parse(indata);
          if (newMsg && newMsg.cmd) {
            hilog.info(DfuServiceInitiator.DOMAIN, DfuServiceInitiator.TAG, `find workermsg! ${JSON.stringify(newMsg)}`);
            workerInstance.postMessage(indata);
          }
          //
          break;
      }
    }
    return new DfuServiceController();
  }

  private init(initFileUri: string,
    initFilePath: string, initFileResId: number): DfuServiceInitiator {
    if (DfuBaseService.MIME_TYPE_ZIP === this.mimeType) {
      throw "Init file must be located inside the ZIP";
    }

    this.initFileUri = initFileUri;
    this.initFilePath = initFilePath;
    this.initFileResId = initFileResId;
    return this;
  }

  private initWithType(fileUri: string, filePath: string,
    fileResId: number,  fileType: number, mimeType: string): DfuServiceInitiator {
    this.fileUri = fileUri;
    this.filePath = filePath;
    this.fileResId = fileResId;
    this.fileType = fileType;
    this.mimeType = mimeType;

    // If the MIME TYPE implies it's a ZIP file then the init file must be included in the file.
    if (DfuBaseService.MIME_TYPE_ZIP === this.mimeType) {
      this.initFileUri = null;
      this.initFilePath = null;
      this.initFileResId = 0;
    }
    return this;
  }

  public static createDfuNotification(dfu_channel_name: string,
    dfu_channel_description: string, showBadge: boolean) {
    // TODO: whether notification is needed
  }

  public static createDfuNotificationChannel() {
    DfuServiceInitiator.createDfuNotification(DfuServiceInitiator.DFU_CHANNEL_NAME,
      DfuServiceInitiator.DFU_CHANNEL_DESCRIPTION, false);
  }

}